package org.sensornet2010.co2sh.prototype;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;

public class TestWavParser {

	/**
	 * Creates a Hashtable
	 * @param wavBinaryContent		byte[]
	 * @return wav					Hashtable						
	 */
	public static Hashtable parseWav(byte[] wavBinaryContent){
		Hashtable wav = new Hashtable();
		TestWavParser parser = new TestWavParser();
		AbstractSoundStream sound;
		try {
			sound = parser.getInstance(wavBinaryContent);
			long samplingRate = sound.getSampleRate();
			int start = 0;
			int end = 16000;//;(int) sound.getDataSize();
			double[] data = sound.readSamples(start, end);
			wav.put("SAMPLING_RATE", samplingRate);
			wav.put("DATA", data);

		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		return wav;
	}
	
	
	/**
	 * Gets an instance of SoundStreamNokia
	 * @param content		byte[]
	 * @return				SoundStreamNokia
	 * @throws Exception
	 */
	private AbstractSoundStream getInstance(byte[] content) throws Exception{
		return new SoundStreamNokia(content);
	}
	
	
	/**
	 * Parses WAV file
	 * @param fileFullPath		String
	 * @return data				double[]
	 */
	private static double[] parseWavFile(String fileFullPath){
		double[] data;
		try {
			TestWavParser parser = new TestWavParser();
			File file = new File(fileFullPath);
			byte[] content = TestWavParser.testGetBytesFromFile(file);
			AbstractSoundStream sound = parser.getInstance(content);
			int bitsXsample = sound.getBitsPerSample();
			double[] samples = sound.readSamples(0, 1024);
			data = samples;	
		}catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		return data;
	}

	
	/**
	 * Gets bytes from a file.
	 * @param file				File
	 * @return bytes			byte[]
	 * @throws IOException
	 */
	public static byte[] testGetBytesFromFile(File file) throws IOException {
		// Code snippet from
		// http://www.java-tips.org/java-se-tips/java.io/reading-a-file-into-a-byte-array.html 
        InputStream is = new FileInputStream(file);
    
        // Get the size of the file
        long length = file.length();
    
        if (length > Integer.MAX_VALUE) {
            // File is too large
        }
    
        // Create the byte array to hold the data
        byte[] bytes = new byte[(int)length];
    
        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length
               && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) {
            offset += numRead;
        }
    
        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file "+file.getName());
        }
        // Close the input stream and return bytes
        is.close();
        return bytes;
    }	
	

	public class SoundStreamNokia extends AbstractSoundStream
	{
		// Code snippet from NoiseTube 
		// http://www.google.com/codesearch/p?hl=en#dZj8F8afubQ/trunk/mobileapp/java/src/noisetube/audio/java/AbstractSoundStream.java 

	        public SoundStreamNokia(byte[] s) throws Exception
	        {
	                super(s);
	        }

	        protected byte readByte(int l) throws Exception
	        {
	                return data[l];
	        }

	        protected int readInt(int i) throws Exception
	        {
	                return (int) byteArrayToLong(data, i);
	        }

	        /**
	         * @param l
	         *            index of the sample
	         * @return
	         * @throws IOException
	         */
	        protected int readShort(int l) throws Exception
	        {
	                return (short) ((data[l + 1] & 0xff) << 8 | data[l] & 0xff);
	        }

	        // ===========================
	        // CONVERT BYTES TO JAVA TYPES
	        // ===========================

	        // these two routines convert a byte array to a unsigned short
	        public int byteArrayToInt(byte[] b, int start)
	        {
	                int low = b[start] & 0xff;
	                int high = b[start + 1] & 0xff;
	                return (short) (high << 8 | low);
	        }

	        // these two routines convert a byte array to an unsigned integer
	        public long byteArrayToLong(byte[] b, int start)
	        {

	                int i = 0;
	                int len = 4;
	                int cnt = 0;
	                byte[] tmp = new byte[len];
	                for(i = start; i < (start + len); i++)
	                {
	                        tmp[cnt] = b[i];
	                        cnt++;
	                }
	                long accum = 0;
	                i = 0;
	                for(int shiftBy = 0; shiftBy < 32; shiftBy += 8)
	                {
	                        accum |= ((long) (tmp[i] & 0xff)) << shiftBy;
	                        i++;
	                }
	                return accum;
	        }
	}
	
	public abstract class AbstractSoundStream
	{

	        private int format;
	        private int channels;
	        private long chunkSize;
	        private long sampleRate;
	        private long byteRate;
	        private int blockAlign;
	        private int bitsPerSample;
	        private long dataSize;
	        protected byte[] data;
	        private boolean header_read = false;
	        final int HEADER_SIZE = 44;

	        /**
	         * Read the sound file from a array of byte
	         *
	         * @param stream
	         * @throws IOException
	         */
	        public AbstractSoundStream(byte[] stream) throws Exception
	        {

	                if(stream == null)
	                {
	                        throw new IllegalArgumentException("stream null");
	                }

	                if(stream.length <= HEADER_SIZE)
	                {
	                        throw new IllegalArgumentException("Stream not valid (only "
	                                        + stream.length + ")");
	                }

	                dataSize = stream.length - HEADER_SIZE;
	                data = stream;
	        }

	        private void readHeader()
	        {
	                try
	                {
	                        // skip the chunkID
	                        String chunkID = "" + (char) readByte(0) + (char) readByte(1)
	                                        + (char) readByte(2) + (char) readByte(3);

	                        String formatS = "" + (char) readByte(8) + (char) readByte(9)
	                                        + (char) readByte(10) + (char) readByte(11);

	                        // read the audio format. This should be 1 for PCM
	                        format = readShort(20);

	                        // read the # of channels (1 or 2)
	                        channels = readShort(22);

	                        // read the samplerate
	                        sampleRate = readInt(24);

	                        // read the bitspersample
	                        bitsPerSample = readShort(34);

	                        blockAlign = channels * bitsPerSample / 8;

	                        // read the byterate
	                        byteRate = blockAlign * sampleRate;

	                        // read the bitspersample
	                        chunkSize = readInt(40);

	                        // fix bug
	                        if(chunkSize < dataSize)
	                        {

	                                dataSize = chunkSize;
	                        }

	                        header_read = true;

	                }
	                catch(Exception ignore)
	                {
	                	ignore.printStackTrace();
	                }
	        }

	        public double[] readSamples2(int start, int end) throws Exception
	        {
	                double[] samples = new double[end - start + 1];
	                for(int i = 0; i < samples.length; i++)
	                {
	                        int start_byte = getByteIndex(start + i);
	                        // if (d > 32767 || d<0) allowed?
	                        int low = data[start_byte] & 0xff;
	                        int hi = data[start_byte + 1] << 8;
	                        samples[i] = (((double) (hi | low)) / (double) (Short.MAX_VALUE));
	                }
	                return samples;
	        }

	        public double[] readSamples(int start, int end) throws Exception
	        {

	                double[] samples = new double[end - start + 1];

	                // for each sample
	                for(int i = 0; i < samples.length; i++)
	                {

	                        int start_byte = getByteIndex(start + i);

	                        int d = readShort(start_byte);

	                        samples[i] = (d) / 32768D;
	                }
	                return samples;
	        }

	        public byte[] getStream()
	        {
	                return data;
	        }

	        public int getFormat()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return format;
	        }

	        public long getChannels()
	        {
	        	if(!header_read)
	            {
	        		readHeader();
	            }
	            return channels;
	        }

	        public long getSampleRate()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return sampleRate;
	        }

	        public long getByteRate()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return byteRate;
	        }

	        public int getBlockAlign()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return blockAlign;
	        }

	        public int getBitsPerSample()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return bitsPerSample;
	        }

	        public long getDataSize()
	        {
	                if(!header_read)
	                {
	                        readHeader();
	                }
	                return dataSize;
	        }

	        public String toString()
	        {
	                return "format: " + format + "\nsampleRate: " + sampleRate
	                                + "\nchannels: " + channels + "\nbyteRate: " + byteRate
	                                + "\nbitsPerSample: " + bitsPerSample + "\nblockAlign: "
	                                + blockAlign + "\nchunkSize: " + chunkSize + "\ndataSize: "
	                                + dataSize;
	        }

	        protected abstract byte readByte(int l) throws Exception;

	        protected abstract int readShort(int l) throws Exception;

	        protected abstract int readInt(int i) throws Exception;

	        private int getByteIndex(int sample_idx)
	        {
	                return HEADER_SIZE + sample_idx * blockAlign;
	        }
	
	
	}	
}
